<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>手动实现数据双向绑定-Vue(v-model)</title>
	</head>
	<body>
		<div id="app">
			<input type="text" v-model="msg">
			<h3>{{ msg }}</h3>
			<p>{{ name }}</p>
		</div>
	</body>
	
	<script>
		function Vue(options) {
			console.log(options)
			this.$options = options || {}
			this.$el = document.querySelector(options.el)
			this._data = options.data
			this._watcher = {}
			this._observer(this._data)
			this._compile(this.$el)
		}
		Vue.prototype._observer = function (data) {
			Object.keys(data).forEach(key => {
				this._watcher[key] = {
					_directives: []
				}
				let watcher = this._watcher[key]
				let value = data[key]
				Object.defineProperty(data, key, {
					configurable: true,
					enumerable: true,
					get() {
						console.log(`${key}获取的值是${value}`)
						return value
					},
					set(newVal) {
						if (newVal !== value) {
							value = newVal
							watcher._directives.forEach(item => {
								item.update()
							})
						}
					}
				})
			})
		}
		Vue.prototype._compile = function (el) {
			let nodes = el.children
			const len = nodes.length
			for (let i = 0; i < len; ++i) {
				let node = nodes[i]
				if (node.children.length > 0) {
					this._compile(node)
				}
				// 判断是否有v-mode指令
				if (node.hasAttributes('v-model') && node.tagName === 'INPUT') {
					node.addEventListener('input', ((key) => {
						let attVal = node.getAttribute('v-model')
						// 创建watcher 对象，并且将订阅器根据属性放入对应的订阅器集合
						let watcher = new Watcher(node, this, attVal, 'value')
						this._watcher[attVal]._directives.push(watcher)
						return () => {
							this._data[attVal] = nodes[key].value
						}
					})(i))
				}
				// 匹配模板
				let reg = /\{\{\s*(.*?)\s*\}\}/igs
				let txt = node.textContent
				if (reg.test(txt)) {
					node.textContent = txt.replace(reg, (match, placeholder) => {
						//  console.log(match,plachoder)
						let attrVal = placeholder
						let watcher = new Watcher(node, this, attrVal, 'innerHTML')
						this._watcher[attrVal]._directives.push(watcher)
						return placeholder.split('.').reduce((val, key) => {
							return val[key]
						}, this._data)
					})
				}
			}
		}
		
		function Watcher(el, vm, val, attr) {
			this.el = el
			this.vm = vm
			this.val = val
			this.attr = attr
			this.update()
		}
		Watcher.prototype.update = function () {
			this.el[this.attr] = this.vm._data[this.val]
		}
		
		const app = new Vue({
			el: '#app',
			data: {
				msg: '5562',
				name: '法外狂徒-张三'
			}
		})
		console.log(app)
		// app._data.name = '李四'
		// console.log(app._data.name)
	</script>
</html>